#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
#include <functional>
#include "Log.h"
#include "thread_pool.h"
#include "copy_del.h"
using namespace std;

#define default_size 1024
using Func = function<string(string)>; // 该类型下创建的对象就当做参数string返回值string的函数使用
using task_t = function<void()>;       // 该类型下创建的对象就当做无参无返回值的函数使用,可以衔接bind修饰的函数，将参数确定化

class Inet_addr
{
public:
    Inet_addr() {}

    Inet_addr(const struct sockaddr_in &clients)
        : _si(clients), _ip(inet_ntoa(clients.sin_addr)), _port(ntohs(clients.sin_port))
    {
    }
    void print_client_info(const char *buffer)
    {
        cout << "[port:" << _port << " "
             << "ip:" << _ip << "]";
        cout << "client says:" << buffer << endl;
    }
    bool operator==(const Inet_addr &com)
    {
        return _ip == com._ip && _port == com._port;
    }
    const struct sockaddr_in &addr()
    {
        return _si;
    }
    const string &ip()
    {
        return _ip;
    }
    const in_port_t &port()
    {
        return _port;
    }
    ~Inet_addr()
    {
    }

private:
    struct sockaddr_in _si;
    string _ip;
    in_port_t _port;
};

class udp_server : public copy_del
{
    // udp_server a;
    // udp_server b(a);//udp_server继承的copy_del类是一个禁用拷贝构造的类，所以不能调用构造

public:
    udp_server(uint16_t port, Func f)
        : _port(port), _func(f)
    {
    }

    void init()
    {
        // 1.创建套接字（本质就是创建文件细节）
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            lg.Log_infom(Fatal, "创建套接字失败: sockfd=%d,%s", _sockfd, strerror(errno));
            exit(-1);
        }
        lg.Log_infom(Fatal, "创建套接字成功: sockfd=%d", _sockfd);

        // 2.绑定套接字
        struct sockaddr_in local;
        bzero(&local, sizeof(local));       // 全部请0
        local.sin_family = AF_INET;         // socket inet(ip) 协议家族,绑定网络通信的信息
        local.sin_port = htons(_port);      // 将主机端口号序列转成网络
        local.sin_addr.s_addr = INADDR_ANY; // 任意ip地址
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 转成网络序列的四字节ip

        int n = ::bind(_sockfd, (sockaddr *)&local, sizeof(local));
        if (n == -1)
        {
            lg.Log_infom(Fatal, "绑定套接字失败: bind ret=%d,%s", n, strerror(errno));
            exit(-1);
        }
        lg.Log_infom(Fatal, "绑定套接字成功: bind ret=%d", n);

        // 通过线程池来转发客户端发的信息
        thread_pool<task_t>::get_instance();
    }

    void myfunc(const char *tmp) // 接收客户端任务再转发服务器(创建的子线程所执行的）
    {
        unique_lock<mutex> ul(_mtx);
        // 服务端接收消息后是将消息转发给所有的客户端
        for (auto ia : _vipport) // 遍历所有的客户端并依次发送
        {
            sendto(_sockfd, tmp, strlen(tmp), 0, (sockaddr *)&ia.addr(), sizeof(ia.addr()));
            lg.Log_infom(Debug, "服务器发消息成功给ip:%s,port:%d", ia.ip().c_str(), ia.port());
        }
    }

    void start()
    {
        while (1)
        {
            // 客户端的主线程可以一直的收消息，将服务端发送的消息交给创建的线程进行转发处理
            char buffer[default_size];
            struct sockaddr_in clients; // 是一个输入输出型参数，接收消息以后会存入发消息的主机信息
            socklen_t len = sizeof(clients);
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&clients, &len); // 收消息

            // 将客户端主机信息插入进容器，以便转发信息
            Inet_addr ia(clients);
            int i = 0;
            for (i = 0; i < _vipport.size(); i++) // 遍历查找是否是同一个用户发来的消息
            {
                if (_vipport[i] == ia)
                    break;
            }
            if (i == _vipport.size())
                _vipport.push_back(ia);

            if (n > 0)
            {
                // 只有主线程才会执行start函数里的内容，将服务器里的任务都压入线程池相关容器中
                buffer[n] = 0;
                ia.print_client_info(buffer); // 打印用户端发送方的相关ip端口信息

                // 将任务压入进程池的任务管理容器中，在等待队列中的线程会响应并处理
                task_t task = std::bind(&udp_server::myfunc, this, buffer); // 其实就是调用回调函数(bind可以固定参数)
                thread_pool<task_t>::get_instance()->push(task);

                // string messages = _func(buffer); // 对服务器发送的消息进行处理，然后再将处理结果发回去
                // sendto(_sockfd, messages.c_str(), messages.size(), 0, (sockaddr *)&clients, len);
            }
        }
    }

    ~udp_server() {}

private:
    string _ip;
    uint16_t _port;
    int _sockfd;
    Func _func;                 // 回调（就相当于函数指针
    mutex _mtx;                 // 锁
    Inet_addr _idinfo;          // 客户端基本信息
    vector<Inet_addr> _vipport; // 存放所有客户端的ip和端口
};
